package etcd

import (
	"common/config"
	"context"
	"encoding/json"
	clientv3 "go.etcd.io/etcd/client/v3"
	"go.uber.org/zap"
	"time"
)

type Register struct {
	etcdCli     *clientv3.Client                        //ectd连接
	leaseId     clientv3.LeaseID                        //租约id
	DialTimeOut int                                     //超时时间
	ttl         int                                     //租约时间
	keepAliveCh <-chan *clientv3.LeaseKeepAliveResponse //心跳
	info        Server                                  //注册的server信息
	closeCh     chan struct{}                           //设置一个channel,当接收到消息的时候此时进行关闭操作
}

func NewRegister() *Register {
	return &Register{}
}

/*
*
注册
*/
func (r *Register) Register(conf config.EtcdConf) error {
	//1 初始化注册信息
	info := Server{
		Name:    conf.Register.Name,
		Weight:  conf.Register.Weight,
		Addr:    conf.Register.Addr,
		Version: conf.Register.Version,
		Ttl:     conf.Register.Ttl,
	}
	r.info = info
	//2 初始化etcd client
	ectdCli, err := clientv3.New(clientv3.Config{
		Endpoints:   conf.Addrs,
		DialTimeout: time.Duration(conf.DialTimeout),
	})
	if err != nil {
		zap.S().Errorf("init clientv3 err:%v", err)
	}
	r.etcdCli = ectdCli

	//3 进行真正的注册
	if err = r.register(); err != nil {
		zap.S().Errorf("register rto etcd err:%v", err)
	}
	//go 协程进行监控
	r.closeCh = make(chan struct{})
	go r.watch()
	return nil
}

/*
*
关闭
*/
func (r *Register) Close() {
	r.closeCh <- struct{}{}
}

/*
**
真正执行注册的流程
*/
func (r *Register) register() error {
	//返回值第一个为Context 第二个为func
	ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(r.DialTimeOut))
	defer cancel()
	//1 创建租约
	err := r.createLease(ctx, r.info.Ttl)
	if err != nil {
		return err
	}
	//2 创建心跳
	err = r.keepAlive(ctx)
	if err != nil {
		return err
	}
	//3 绑定租约
	marshal, err := json.Marshal(r.info)
	err = r.bindLease(ctx, r.info.BindServerKey(), string(marshal))
	if err != nil {
		return err
	}
	return nil
}

/*
*
创建租约
*/
func (r *Register) createLease(ctx context.Context, ttl int64) error {
	grant, err := r.etcdCli.Grant(ctx, ttl)
	if err != nil {
		zap.S().Errorf("do createLease failed:err:%v", err)
		return err
	}
	r.leaseId = grant.ID
	zap.S().Infof("do createLease success")
	return nil
}

/*
*
绑定租约
*/
func (r *Register) bindLease(ctx context.Context, key string, value string) error {
	_, err := r.etcdCli.Put(ctx, key, value, clientv3.WithLease(r.leaseId))
	if err != nil {
		zap.S().Errorf("do bindLease failed:err:%v", err)
		return err
	}
	zap.S().Infof("do bindLease success,key:%v,value:%v", key, value)
	return nil
}

/*
*
监控
*/
func (r *Register) watch() {
	ticker := time.NewTicker(time.Duration(r.info.Ttl) * time.Second)
	for {
		select {
		case <-ticker.C:
			if r.keepAliveCh == nil {
				if err := r.register(); err != nil {
					zap.S().Errorf("ticker register failed,err:%v", err)
				}
			}
		case <-r.keepAliveCh:
			//todo 什么都不做
		case <-r.closeCh:
			if err := r.unregister(); err != nil {
				zap.S().Errorf("close and unregister failed:err:%v", err)
			}
			//撤销租约
			if _, err := r.etcdCli.Revoke(context.Background(), r.leaseId); err != nil {
				zap.S().Errorf("close and Revoke releaseId failed:err:%v", err)
			}
			if r.etcdCli != nil {
				r.etcdCli.Close()
			}
			zap.S().Info("unregister etcd success")
		}
	}
}

/*
*
进行心跳检测
*/
func (r *Register) keepAlive(ctx context.Context) error {
	keepAliveCh, err := r.etcdCli.KeepAlive(ctx, r.leaseId)
	if err != nil {
		zap.S().Errorf("do keepAlive failed:err:%v", err)
		return err
	}
	r.keepAliveCh = keepAliveCh
	zap.S().Info("do keepAlive success")
	return nil
}

func (r *Register) unregister() error {
	_, err := r.etcdCli.Delete(context.Background(), r.info.BindServerKey())
	return err
}
